#include "stdafx.h"

#ifdef  _DEBUG
#define IL_DEBUG
#endif

/************************************************************************/
/* includes																*/
/************************************************************************/
#include <map>
#include <string>

#include "TextureManager.h"

/************************************************************************/
/* link with the DevIL libraries										*/
/************************************************************************/
#pragma comment(lib,"Devil.lib")
#pragma comment(lib,"ilu.lib")
#pragma comment(lib,"ilut.lib")




cTextureManager* cTextureManager::m_pInstanceSingleton=NULL;




HTexture GetTexture(const char* name)
{
	return cTextureManager::GetInstance()->GetTexture(name);
}


GLuint DereferenceTexture(HTexture htex)
{
	return cTextureManager::GetInstance()->DereferenceTexture(htex);
}

void BindTexture(HTexture htex)
{
	GLuint id = cTextureManager::GetInstance()->DereferenceTexture(htex);
	glBindTexture(GL_TEXTURE_2D,id);
}

void ReleaseTexture(HTexture htex)
{
	cTextureManager::GetInstance()->ReleaseTexture(htex);
}

//////////////////////////////////////////////////////////////////////////
cTextureManager* cTextureManager::GetInstance()
{
	if (cTextureManager::m_pInstanceSingleton==NULL)
		cTextureManager::m_pInstanceSingleton = new cTextureManager;
	return cTextureManager::m_pInstanceSingleton;
}

//////////////////////////////////////////////////////////////////////////
cTextureManager::~cTextureManager()
{
	DeleteAllTextures();
}


//////////////////////////////////////////////////////////////////////////
void cTextureManager::DeleteInstance()
{
	delete cTextureManager::m_pInstanceSingleton;
	cTextureManager::m_pInstanceSingleton=NULL;
}


//////////////////////////////////////////////////////////////////////////
cTextureManager::cTextureManager()
{
	InitDevIL();	
}



//////////////////////////////////////////////////////////////////////////
bool cTextureManager::InitDevIL()
{
	//check if the shared lib's version matches the executable's version.
	if (ilGetInteger(IL_VERSION_NUM) < IL_VERSION ||
		ilGetInteger(ILU_VERSION_NUM) < ILU_VERSION ||
		ilGetInteger(ILUT_VERSION_NUM) < ILUT_VERSION) 
	{
		Log("Got problem with DevIL lib versions.... ");
	}
	ilInit();						// Needed to initialize DevIL.	
	ilEnable(IL_CONV_PAL);			// GL cannot use palettes anyway, so convert early.	
	ilutEnable(ILUT_OPENGL_CONV);	// Gets rid of dithering on some nVidia-based cards.	
	ilutRenderer(ILUT_OPENGL);		// Lets ILUT know to use its OpenGL functions.

	return true;
}



//////////////////////////////////////////////////////////////////////////
void cTextureManager::ReleaseTexture(HTexture htex)
{
	// assert( m_textures.GetRefCount(texture)!=0 );
	if(m_textures.GetRefCount(htex)==0)
		return;

	Texture* ptex = m_textures.Dereference(htex);

	assert( ptex );
	
	m_textures.Release(htex);

	if (m_textures.GetRefCount(htex)==0)
	{
		m_NameIndex.erase( m_NameIndex.find(ptex->name));
		if(glIsTexture(ptex->id)) 
			glDeleteTextures(1,&ptex->id);
	}
}



//////////////////////////////////////////////////////////////////////////
HTexture cTextureManager::GetTexture(const char* name)
{
	// insert/find
	NameIndexInsertRecord rc = 
		m_NameIndex.insert(make_pair(name,HTexture()));


	// if this handle already exists in m_textures, it will
	// increment its reference count, else will allocate it
	Texture* ptex = m_textures.Acquire(rc.first->second);
	
	if (rc.second)	// this is new insertion, so load the texture
	{
		// init the texture data...
		ptex->name = name;
		ptex->id = 0;
		if( !LoadTexture(ptex) )
		{
			m_textures.Release(rc.first->second);
			m_NameIndex.erase( m_NameIndex.find(ptex->name));
			rc.first->second = HTexture();
		}
	}

	return rc.first->second;
}

//////////////////////////////////////////////////////////////////////////
// 
HTexture cTextureManager::GetCube(char* names[6], bool bCompression)
{
	// insert/find
	// we put as texture name the first of the filenames....
	// not the best way but... how many cubes will we aquire anyway?
	NameIndexInsertRecord rc = 
		m_NameIndex.insert(make_pair(names[0],HTexture()));


	// if this handle already exists in m_textures, it will
	// increment its reference count, else will allocate it
	Texture* ptex = m_textures.Acquire(rc.first->second);

	if (rc.second)	// this is new insertion, so load the texture
	{
		// init the texture data...
		ptex->name = names[0];
		ptex->id = 0;
		if( !LoadCube(ptex,names,bCompression))
		{
			m_textures.Release(rc.first->second);
			m_NameIndex.erase( m_NameIndex.find(ptex->name));
			rc.first->second = HTexture();
		}
	}

	return rc.first->second;
}

//////////////////////////////////////////////////////////////////////////
bool cTextureManager::LoadTexture(Texture* ptex)
{
	ILuint ImgId = 0;	// IL image ID

	// Generate the main image name to use.
	ilGenImages(1, &ImgId);
	// Bind this image name.
	ilBindImage(ImgId);

	// Loads the image specified by filename into the ImgId image.
	char fn[256];
	strcpy(fn,ptex->name.c_str());
	if (!ilLoadImage(fn))
	{
		//Log("cTextureManager::LoadTexture: no image %s found",name);
		return false;
	}

	// for some reason, il shows the images upside-down
	// so flip it from the beginning to show them correctly
	iluFlipImage();

	ptex->id = ilutGLBindMipmaps();
	if(ptex->id==0)
		return false;

	assert(glIsTexture(ptex->id));

	// We're done with our image, so we go ahead and delete it.
	ilDeleteImages(1, &ImgId);
	return true;
}


//////////////////////////////////////////////////////////////////////////
bool cTextureManager::LoadCube(Texture* ptex,char* filenames[6],bool bCompression)
{
	//
	// check if cube mapping is supported, if not return false
	//
	if (!GLEW_ARB_texture_cube_map)
	{	
		return false;
	}


	GLint	clampMode;
	GLuint id;
	ILuint ImgId[6];	// IL image ID

	//
	//	load the images
	//
	ilGenImages(6,ImgId);
	for(int i=0; i<6; i++)
	{
		char fn[256];
		strcpy(fn,filenames[i]); 	
		ilBindImage( ImgId[i] );
		if (!ilLoadImage(fn) )
			Log("cTextureManager::loadCube: no image %s found",fn);
		//iluFlipImage();
	}
	

	glGenTextures(1,&id);
	glBindTexture(GL_TEXTURE_CUBE_MAP_ARB,id);

	// Use GL_CLAMP_TO_EDGE if we can
	if(GLEW_EXT_texture_edge_clamp)
		clampMode = GL_CLAMP_TO_EDGE;
	else
		clampMode = GL_CLAMP;

	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,clampMode);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,clampMode);

	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);

	
	
	GLint	format;
	GLuint	position;
	GLint	internalformat;
	for (int i=0; i<6; i++)
	{
		switch(i)
		{
		case 0:	position=GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB;
		case 1: position=GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB;
		case 2:	position=GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB;
		case 3: position=GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB;
		case 4:	position=GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB;
		case 5: position=GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB;
		}

		ilBindImage( ImgId[i] );
		int bytesPerPixel = ilGetInteger(IL_IMAGE_BYTES_PER_PIXEL);

		if (bytesPerPixel==1)
		{
			format=GL_ALPHA;
			if (bCompression && GLEW_ARB_texture_compression)
				internalformat=GL_COMPRESSED_ALPHA_ARB;
			else
				internalformat=GL_ALPHA8;
		}
		else if (bytesPerPixel==3)
		{
			/*if( EXT_bgra_supported )
			format = GL_BGR_EXT;
			else*/
			format = GL_RGB;

			if (bCompression && GLEW_ARB_texture_compression)
				internalformat=GL_COMPRESSED_RGB_ARB;
			else
				internalformat=GL_RGB8;
		}
		else if (bytesPerPixel==4)
		{
			if( GLEW_EXT_bgra )
				format = GL_BGRA_EXT;
			else
				format = GL_RGBA;

			if (bCompression && GLEW_ARB_texture_compression)
				internalformat=GL_COMPRESSED_RGBA_ARB;
			else
				internalformat=GL_RGBA8;
		}

		if( position==GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB || 
			position==GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB || 
			position==GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB || 
			position==GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB )
		{
			iluMirror();
		}

		if(GLEW_SGIS_generate_mipmap)
		{
			glHint(GL_GENERATE_MIPMAP_HINT_SGIS, GL_NICEST);
			glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_GENERATE_MIPMAP_SGIS, TRUE);
			glTexImage2D(position, 0, internalformat, 
				ilGetInteger(IL_IMAGE_WIDTH), 
				ilGetInteger(IL_IMAGE_HEIGHT), 0, format, 
				GL_UNSIGNED_BYTE, ilGetData());
		}
		else
		{
			gluBuild2DMipmaps(position, internalformat, 
				ilGetInteger(IL_IMAGE_WIDTH), 
				ilGetInteger(IL_IMAGE_HEIGHT),
				format, GL_UNSIGNED_BYTE, ilGetData());
		}
	}


	ptex->id = id;
	if(ptex->id==0)
		return false;

	assert(glIsTexture(ptex->id));

	ilDeleteImages(6,ImgId);	// clear loaded images
	return true;
}


//////////////////////////////////////////////////////////////////////////
void cTextureManager::DeleteAllTextures()
{
	Texture *ptex;
	NameIndex::iterator iter = m_NameIndex.begin();
	while (iter != m_NameIndex.end())
	{
		ptex = m_textures.Dereference(iter->second);
		// assert(glIsTexture(ptex->id));
		assert(ptex->name == iter->first);
		glDeleteTextures(1,&ptex->id);
		m_textures.Release(iter->second);
		iter++;
	}
}